/*
 * =====================================================================================
 *    Description:  new interface that can be connect all kinds of terminal
 *
 *        Version:  1.0
 *        Created:  10/10/2011 
 *
 *         Author:  Zhenjiang Dong
 *         School:  Georgia Institute of Technology
 *
 * =====================================================================================
 */

#ifndef  MANIFOLD_IRIS_TORUS_H
#define  MANIFOLD_IRIS_TORUS_H

#include "../interfaces/genericHeader.h"
#include "../interfaces/genericIrisInterface.h"
#include "../components/simpleRouter.h"

namespace manifold {
namespace iris {

template <typename T>
class Torus
{
    public:
        //constructor and deconstructor
        Torus (manifold::kernel::Clock& clk, torus_init_params* params, const Terminal_to_net_mapping* mapping, uint lp_inf, uint lp_rt);
        ~Torus ();

        //connect all components together
        void connect_interface_routers(void);
        void connect_routers(void);
        
	const std::vector <GenNetworkInterface<T>*>& get_interfaces() { return interfaces; }

        //the interfaces' component id
        const std::vector <manifold::kernel::CompId_t>& get_interface_id() { return interface_ids; }
        
	void print_stats(std::ostream&);

#ifndef IRIS_TEST
    private:
#endif
       // variables
       const unsigned x_dim; // x dimension
       const unsigned y_dim; // y dimension
       manifold::kernel::Clock& clk_t; //the clock for routers and interfaces 
       std::vector <SimpleRouter*> routers; //the routers
       std::vector <GenNetworkInterface<T>* > interfaces; //the interfaces
       std::vector <manifold::kernel::CompId_t> router_ids; //the routers' component ID
       std::vector <manifold::kernel::CompId_t> interface_ids; //the interfaces' component ID  
       
#ifdef IRIS_TEST  
       //trace file that using in debugging     
       std::ofstream outFile_data; 
       std::ofstream outFile_signal;
#endif  
     
   protected:     
}; 

//! response for creating the torus topology 
//! @param \c clk  The clock passing from callor
//! @param \c params  The configure parameters for torus network
//! @param \c lp_inf  The logic process id for interfaces
//! @param \c lp_rt  The logic process id for routers
template <typename T>
Torus<T>::Torus(manifold::kernel::Clock& clk, torus_init_params* params, const Terminal_to_net_mapping* mapping, uint lp_inf, uint lp_rt) :
    x_dim(params->x_dim),
    y_dim(params->y_dim),
    clk_t(clk)
{
#ifdef IRIS_TEST
    uint grid_count = 0;
    
    //open trace file
    outFile_data.open("torus_data.tr");

    //handle exception
    if(outFile_data.fail())
       std::cout<<"fail to open file"<<std::endl;
       
    //open trace file
    outFile_signal.open("torus_signal.tr");

    //handle exception
    if(outFile_signal.fail())
       std::cout<<"fail to open file"<<std::endl;   
#endif

    
    //parameters for interface
    inf_init_params i_p_inf;
    
    i_p_inf.linkWidth = params->link_width;
    i_p_inf.num_credits = params->credits;
    i_p_inf.num_vc = params->no_vcs; 
    

    //parameters for router
    router_init_params i_p_rt;

    i_p_rt.no_nodes = params->x_dim * params->y_dim;
    i_p_rt.grid_size = params->x_dim;
    i_p_rt.no_ports = 5;
    i_p_rt.no_vcs = params->no_vcs;
    i_p_rt.credits = params->credits;
    i_p_rt.rc_method = TORUS_ROUTING; 
    
    const unsigned no_nodes = x_dim * y_dim;
    //creat interfaces and routers
    for ( uint i=0; i< no_nodes; i++)
    {
        interface_ids.push_back( manifold::kernel::Component::Create< GenNetworkInterface<T> >(lp_inf, i, mapping, &i_p_inf) );
#ifdef IRIS_TEST
        outFile_data<< "0.0 N " << i << " " << i << " " << 0 << std::endl;
        outFile_signal<< "0.0 N " << i << " " << i << " " << 0 << std::endl;
#endif        
        router_ids.push_back( manifold::kernel::Component::Create<SimpleRouter>(lp_rt, i, &i_p_rt) ); 
#ifdef IRIS_TEST
        if ((i%x_dim != 0) && (i%x_dim != (x_dim - 1)))
        {
           if (grid_count%2 == 0)
           {
              outFile_data<< "0.0 N " << i  + no_nodes << " " << i%x_dim + 6<< " " << 1 + grid_count*2<< std::endl;
              outFile_signal<< "0.0 N " << i  + no_nodes << " " << i%x_dim + 6<< " " << 1 + grid_count*2<< std::endl;
           }
           if (grid_count%2 == 1)
           {
              outFile_data<< "0.0 N " << i  + no_nodes << " " << double(i%x_dim) + 6 + 0.5<< " " << 1 + grid_count*2<< std::endl;
              outFile_signal<< "0.0 N " << i  + no_nodes << " " << double(i%x_dim) + 6 + 0.5<< " " << 1 + grid_count*2<< std::endl;
           }           
        }
        else
        {
           if (grid_count%2 == 0)
           {
               outFile_data<< "0.0 N " << i  + no_nodes << " " << i%x_dim + 6<< " " << 2 + grid_count*2 << std::endl;
               outFile_signal<< "0.0 N " << i  + no_nodes << " " << i%x_dim + 6<< " " << 2 + grid_count*2 << std::endl;
           }  
           if (grid_count%2 == 1)
           {
               outFile_data<< "0.0 N " << i  + no_nodes << " " << double(i%x_dim) + 6 + 0.5<< " " << 2 + grid_count*2 << std::endl;
               outFile_signal<< "0.0 N " << i  + no_nodes << " " << double(i%x_dim) + 6 + 0.5<< " " << 2 + grid_count*2 << std::endl;
           }  
        }
        
        if (i%x_dim == (x_dim - 1))
           grid_count++;
#endif        
    }
    
    //register interfaces to clock
    for ( uint i=0; i< interface_ids.size(); i++)
    {
        GenNetworkInterface<T>* interface = manifold::kernel::Component::GetComponent< GenNetworkInterface<T> >(interface_ids.at(i));
        if ( interface != NULL )
        {
            manifold::kernel::Clock::Register< GenNetworkInterface<T> >
            (clk_t, interface, &GenNetworkInterface<T>::tick, &GenNetworkInterface<T>::tock); //pass clock from out side
        }
	interfaces.push_back(interface);
    }
    
    //register router to clock
    for ( uint i=0; i< router_ids.size(); i++)
    {
        SimpleRouter* rr= manifold::kernel::Component::GetComponent<SimpleRouter>(router_ids.at(i));
        if ( rr != NULL )
        {
            manifold::kernel::Clock::Register<SimpleRouter>(clk_t, rr, &SimpleRouter::tick, &SimpleRouter::tock);
        }
	routers.push_back(rr);
    }
     

    //establish the connections
    connect_interface_routers();
    connect_routers();
}

//deconstructor
template <typename T>
Torus<T>::~Torus()
{
    for ( uint i=0 ; i<x_dim * y_dim; i++ )
    {
        delete interfaces[i];
        delete routers[i];
    }

}

//! connect interface to router
template <typename T>
void
Torus<T>::connect_interface_routers()
{
    int LATENCY = 1;
    //  Connect for the output links of the router 
    for( uint i=0; i<x_dim*y_dim; i++)
    {
        //uint iid = interface_ids.at(i);
        //uint rid = router_ids.at(i);

        // going west  <-
        //  Interface DATA 
        manifold::kernel::Manifold::Connect(interface_ids.at(i), GenNetworkInterface<T>::DATAOUT, 
                                            router_ids.at(i), SimpleRouter::PORT_NI,
                                            &SimpleRouter::handle_link_arrival , static_cast<manifold::kernel::Ticks_t>(LATENCY));
        manifold::kernel::Manifold::Connect(router_ids.at(i), SimpleRouter::PORT_NI, 
                                            interface_ids.at(i), GenNetworkInterface<T>::DATAIN,
                                            &GenNetworkInterface<T>::handle_router , static_cast<manifold::kernel::Ticks_t>(LATENCY));
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << interfaces.at(i)->id << " " << routers.at(i)->node_id + x_dim*y_dim<< std::endl;
#endif
    }
}


//! connect routers together
template <typename T>
void
Torus<T>::connect_routers()
{
    const manifold::kernel::Ticks_t LATENCY = 1;

#ifdef IRIS_TEST
    const unsigned no_nodes = x_dim * y_dim;
#endif

    // Configure east - west links for the routers.. in order first WEST then
    for ( uint i=0; i<y_dim; i++)  //for all rows
        for ( uint j=1; j<x_dim; j++)  //for all columns except the left-most
        {
            uint rno = router_ids.at(i*x_dim + j);
            uint rno2 = router_ids.at(i*x_dim + j-1);

            // going west  <-
            // Router->Router DATA 
            manifold::kernel::Manifold::Connect(rno, SimpleRouter::PORT_WEST, 
                                                rno2, SimpleRouter::PORT_EAST,
                                                &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        const unsigned no_nodes = x_dim * y_dim;
        outFile_data<< "0.0 L " << routers.at(i*x_dim + j)->node_id + no_nodes << " " 
        << routers.at(i*x_dim + j-1)->node_id + no_nodes<< std::endl;
#endif
            // going east  ->
            // Router->Router DATA 
            manifold::kernel::Manifold::Connect(rno2, SimpleRouter::PORT_EAST, 
                                                rno, SimpleRouter::PORT_WEST,
                                                &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i*x_dim + j)->node_id + no_nodes<< " " 
        << routers.at(i*x_dim + j-1)->node_id + no_nodes<< std::endl;
#endif
        }

    //connect north south
    for ( uint i=1; i<y_dim; i++) { //starting from 2nd row
        for ( uint j=0; j<x_dim; j++) //for all columns
        {
            uint rno = router_ids.at(i*x_dim + j);
            uint up_rno =router_ids.at((i-1)*x_dim + j);

            // going north ^
            // Router->Router DATA 
            manifold::kernel::Manifold::Connect(rno, SimpleRouter::PORT_NORTH, 
                                                up_rno, SimpleRouter::PORT_SOUTH,
                                                &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i*x_dim + j)->node_id + no_nodes<< " " 
        << routers.at(i*x_dim + j- x_dim)->node_id + no_nodes<< std::endl;
#endif
            // going south->
            // Router->Router DATA 
            manifold::kernel::Manifold::Connect(up_rno, SimpleRouter::PORT_SOUTH, 
                                                rno, SimpleRouter::PORT_NORTH,
                                                &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i*x_dim + j)->node_id + no_nodes<< " " 
        << routers.at(i*x_dim + j- x_dim)->node_id + no_nodes<< std::endl;
#endif
        }
    }


    // connect edge nodes East-west
    for ( uint i=0; i<y_dim; i++)
    {
        uint rno = router_ids.at(i*x_dim);
        uint end_rno = router_ids.at(i*x_dim+x_dim-1);

        // router 0 and end router of every row
        // going west <-
        manifold::kernel::Manifold::Connect(rno, SimpleRouter::PORT_WEST, 
                                            (end_rno), SimpleRouter::PORT_EAST,
                                            &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i*x_dim)->node_id + no_nodes<< " " 
        << routers.at(i*x_dim + x_dim-1)->node_id + no_nodes<< std::endl;
#endif

        // going east ->
        manifold::kernel::Manifold::Connect(end_rno, SimpleRouter::PORT_EAST, 
                                            rno, SimpleRouter::PORT_WEST,
                                            &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i*x_dim)->node_id + no_nodes<< " " 
        << routers.at(i*x_dim + x_dim-1)->node_id + no_nodes<< std::endl;
#endif                                            
    }


    // connect edge nodes north-south
    for ( uint i=0; i<x_dim; i++) //for all columns
    {
        uint rno = router_ids.at(i);
        uint end_rno = router_ids.at((y_dim-1)*x_dim+i);

        // router 0 and end router of every row
        // going west <-
        manifold::kernel::Manifold::Connect((rno), SimpleRouter::PORT_NORTH, 
                                            (end_rno), SimpleRouter::PORT_SOUTH,
                                            &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i)->node_id + no_nodes<< " " 
        << routers.at((no_nodes/x_dim-1)*x_dim+i)->node_id + no_nodes<< std::endl;
#endif

        // going east ->
        manifold::kernel::Manifold::Connect(end_rno, SimpleRouter::PORT_SOUTH, 
                                            rno, SimpleRouter::PORT_NORTH,
                                            &SimpleRouter::handle_link_arrival, LATENCY);
#ifdef IRIS_TEST
        outFile_data<< "0.0 L " << routers.at(i)->node_id + no_nodes<< " " 
        << routers.at((no_nodes/x_dim-1)*x_dim+i)->node_id + no_nodes<< std::endl;
#endif                                            
    }

}

template <typename T>
void Torus<T> :: print_stats(std::ostream& out)
{
    for(unsigned i=0; i<interfaces.size(); i++) {
	if(interfaces[i])
	    interfaces[i]->print_stats(out);
    }

    for(unsigned i=0; i<routers.size(); i++) {
	if(routers[i])
	    out << routers[i]->print_stats();
    }
}


} //Iris
} //Manifold

#endif  
/* ----- #ifndef MANIFOLD_IRIS_TORUS_H  ----- */
